# 导入必要的库
import json                  # 用于JSON数据的序列化和反序列化
import time                  # 用于时间相关操作（延迟、计时等）
import threading             # 用于多线程处理（并发执行任务）
import queue                 # 用于线程安全的队列（任务调度）
import logging               # 用于系统日志记录
from hqyj_mqtt import Mqtt_Clt  # 自定义MQTT客户端（用于设备通信）
import base64                # 用于Base64编码解码（处理图像数据）
import numpy as np           # 用于数值计算（处理图像数组）
import cv2                   # 用于图像处理（解码、显示等）
from predict import predict_yolo  # 导入YOLO模型预测函数（桃子成熟度分类）

# 新增PyQt5相关导入（用于GUI界面）
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout,
                             QTextEdit, QPushButton, QWidget, QLabel, QGroupBox,
                             QScrollArea, QGridLayout)
from PyQt5.QtCore import pyqtSignal, QObject, Qt, QTimer  # Qt核心功能（信号、定时器等）
from PyQt5.QtGui import QFont, QTextCursor  # Qt GUI相关（字体、文本光标）


# 配置日志 - 自定义日志处理器，将日志发送到Qt界面
class QtLogHandler(logging.Handler, QObject):
    # 定义信号，用于将日志消息发送到UI线程
    log_signal = pyqtSignal(str)

    def __init__(self):
        logging.Handler.__init__(self)  # 初始化日志处理器
        QObject.__init__(self)          # 初始化QObject（支持信号槽）
        # 设置日志格式：时间 [级别] 消息
        self.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))

    def emit(self, record):
        # 重写emit方法，将日志记录格式化为字符串并通过信号发送
        msg = self.format(record)
        self.log_signal.emit(msg)


# 设置日志记录器
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)  # 设置日志级别为INFO（记录INFO及以上级别日志）

# 文件处理器：将日志写入文件
file_handler = logging.FileHandler('cargo_sorting.log')
file_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
logger.addHandler(file_handler)

# Qt处理器：将日志显示在界面上
qt_handler = QtLogHandler()
logger.addHandler(qt_handler)

# 推杆与传感器定义（硬件映射：名称对应实际物理设备）
load_rod = "first"               # 上货推杆名称
ripe_rod = "second"              # 成熟桃子推杆名称
half_ripe_rod = "third"          # 半熟桃子推杆名称
raw_rod = "fourth"               # 生桃子推杆名称
FIRST_SWITCH = "first_switch"    # 对应成熟桃子的传感器
SECOND_SWITCH = "second_switch"  # 对应半熟桃子的传感器
THIRD_SWITCH = "third_switch"    # 对应生桃子的传感器

# 传感器触发顺序（用于处理多个传感器同时触发的情况）
SENSOR_ORDER = ["first_switch", "second_switch", "third_switch"]
# 分类与传感器的映射（哪种分类对应哪个传感器）
CLASS_SWITCH_MAP = {
    "ripe": FIRST_SWITCH,        # 成熟桃子对应第一个传感器
    "half-ripe": SECOND_SWITCH,  # 半熟桃子对应第二个传感器
    "raw": THIRD_SWITCH          # 生桃子对应第三个传感器
}
# 分类与推杆的映射（哪种分类对应哪个推杆）
CLASS_ROD_MAP = {
    "ripe": ripe_rod,            # 成熟桃子对应第二个推杆
    "half-ripe": half_ripe_rod,  # 半熟桃子对应第三个推杆
    "raw": raw_rod               # 生桃子对应第四个推杆
}

# 系统配置参数
PUSH_DELAY = 0.2                 # 推杆动作延迟时间（秒）
RECOGNIZE_INTERVAL = 2           # 上货识别间隔（秒）
ROD_ACTION_TIME = {              # 各推杆动作持续时间（推/拉的执行时间）
    "first": 0.6,
    "second": 0.6,
    "third": 0.6,
    "fourth": 0.6
}
SENSOR_TIMEOUT = 8.0             # 传感器触发后重置超时时间（秒）
CARGO_TIMEOUT = 30.0             # 货物处理超时时间（秒，超过则视为异常并移除）

# 任务队列（线程安全，用于调度推杆任务）
load_rod_queue = queue.Queue(maxsize=5)   # 上货推杆任务队列（最大5个任务）
other_rod_queue = queue.Queue(maxsize=10) # 分类推杆任务队列（最大10个任务）

# 上货相关的线程锁和定时器（控制上货频率，避免冲突）
load_lock = threading.Lock()      # 上货操作的线程锁（保证线程安全）
load_timer = None                 # 上货定时器（用于控制上货间隔）
last_load_time = 0.0              # 上次上货时间（用于计算间隔）
last_sensor_trigger_time = {}     # 传感器最近一次触发时间（去抖动）


class CargoTracker:
    """货物追踪器：管理货物的生命周期、状态和统计信息"""
    def __init__(self):
        self.cargo_map = {}               # 存储货物信息的字典（key:货物ID，value:详细信息）
        self.next_cargo_id = 1            # 下一个货物的ID（自增）
        # 统计计数器（记录各分类的处理数量）
        self.statistics = {
            "ripe": 0,
            "half-ripe": 0,
            "raw": 0
        }
        self.statistics_updated = threading.Event()  # 统计更新事件（用于通知界面更新）

    def create_new_cargo(self, classification):
        """创建新货物并记录分类信息"""
        cargo_id = self.next_cargo_id
        self.next_cargo_id += 1  # ID自增

        # 存储货物详细信息
        self.cargo_map[cargo_id] = {
            "classification": classification,  # 分类结果（ripe/half-ripe/raw）
            "triggered_switch": None,          # 触发的传感器（未触发时为None）
            "status": "pending",               # 状态（pending:待处理；processing:处理中）
            "create_time": time.time(),        # 创建时间（用于超时判断）
            "sensor_triggered": False          # 传感器是否已触发
        }

        logger.info(
            f"生成新货物ID: {cargo_id}, 分类: {classification}, 应触发传感器: {CLASS_SWITCH_MAP[classification]}")
        return cargo_id

    def update_cargo_switch(self, switch_name):
        """更新货物触发的传感器（匹配分类对应的传感器）"""
        current_time = time.time()
        # 传感器去抖动：0.2秒内连续触发则忽略（避免硬件抖动）
        if switch_name in last_sensor_trigger_time and (current_time - last_sensor_trigger_time[switch_name] < 0.2):
            logger.warning(f"传感器 {switch_name} 触发过于频繁，忽略")
            return None
        last_sensor_trigger_time[switch_name] = current_time

        # 遍历货物，找到匹配的待处理货物（分类对应传感器且未触发过）
        for cargo_id, cargo_info in list(self.cargo_map.items()):
            if (cargo_info["status"] == "pending" and
                    CLASS_SWITCH_MAP.get(cargo_info["classification"]) == switch_name and
                    not cargo_info["sensor_triggered"]):
                # 更新货物状态
                cargo_info["triggered_switch"] = switch_name
                cargo_info["status"] = "processing"
                cargo_info["sensor_triggered"] = True
                # 启动定时器：超时后重置传感器状态
                threading.Timer(SENSOR_TIMEOUT, self.reset_sensor, args=(switch_name, cargo_id)).start()
                logger.info(f"货物ID: {cargo_id}（{cargo_info['classification']}）触发匹配传感器: {switch_name}")
                return cargo_id
        logger.warning(f"无匹配分类的待处理货物，忽略传感器: {switch_name}")
        return None

    def reset_sensor(self, switch_name, cargo_id):
        """重置传感器状态（超时未处理时调用）"""
        if cargo_id in self.cargo_map and self.cargo_map[cargo_id]["triggered_switch"] == switch_name:
            self.cargo_map[cargo_id]["sensor_triggered"] = False
            logger.info(f"货物ID: {cargo_id} 的传感器 {switch_name} 已重置")

    def mark_cargo_done(self, cargo_id):
        """标记货物处理完成，并更新统计信息"""
        if cargo_id in self.cargo_map:
            # 更新统计：增加对应分类的计数
            classification = self.cargo_map[cargo_id]["classification"]
            if classification in self.statistics:
                self.statistics[classification] += 1
                self.statistics_updated.set()  # 触发统计更新事件

            # 重置传感器并删除货物记录
            switch_name = self.cargo_map[cargo_id]["triggered_switch"]
            if switch_name:
                self.reset_sensor(switch_name, cargo_id)
            logger.info(f"货物ID: {cargo_id} 处理完成")
            del self.cargo_map[cargo_id]

    def cleanup_old_cargos(self):
        """清理超时未处理的货物（防止内存泄漏）"""
        current_time = time.time()
        to_remove = []
        for cargo_id, cargo_info in self.cargo_map.items():
            if current_time - cargo_info["create_time"] > CARGO_TIMEOUT:
                logger.warning(f"货物ID: {cargo_id} 超时未处理，移除")
                to_remove.append(cargo_id)
        # 批量删除超时货物
        for cargo_id in to_remove:
            del self.cargo_map[cargo_id]

    def get_statistics(self):
        """获取当前统计信息的副本（避免线程安全问题）"""
        return self.statistics.copy()


def safe_send_json(mqtt_client, msg):
    """安全发送JSON消息（包含异常处理和重连逻辑）"""
    try:
        mqtt_client.send_json_msg(msg)
    except Exception as e:
        logger.error(f"MQTT 发送失败: {e}")
        try:
            mqtt_client.reconnect()  # 发送失败时尝试重连
        except:
            logger.error("MQTT 重连失败")


def push_pull(rod, mqtt_client, delay=0):
    """执行推杆的推和拉动作（控制物理设备）"""
    if delay > 0:
        logger.info(f"等待 {delay:.2f}秒 后执行推杆 {rod}")
        time.sleep(delay)
    logger.info(f"执行推杆 {rod} 动作")

    try:
        # 发送推杆"推"命令
        safe_send_json(mqtt_client, json.dumps({"rod_control": f"{rod}_push"}))
        logger.info(f"发送 {rod}_push 命令")
        time.sleep(ROD_ACTION_TIME[rod])  # 等待推动作完成
        # 发送推杆"拉"命令（复位）
        safe_send_json(mqtt_client, json.dumps({"rod_control": f"{rod}_pull"}))
        logger.info(f"发送 {rod}_pull 命令")
    except Exception as e:
        logger.error(f"推杆 {rod} 命令失败: {e}")


def load_rod_worker(mqtt_client):
    """上货推杆工作线程（处理上货任务队列）"""
    while True:
        try:
            # 从队列获取任务（超时5秒，避免无限阻塞）
            rod = load_rod_queue.get(timeout=5.0)
            if rod is None:  # 收到终止信号
                break
            # 执行推杆动作
            push_pull(rod, mqtt_client)
            load_rod_queue.task_done()  # 标记任务完成
            logger.info(f"上货推杆 {rod} 任务完成，当前队列大小: {load_rod_queue.qsize()}")
        except queue.Empty:
            continue  # 队列为空时继续等待
        except Exception as e:
            logger.error(f"load_rod_worker 错误: {e}")


def other_rod_worker(mqtt_client, tracker):
    """分类推杆工作线程（处理分类任务队列）"""
    while True:
        try:
            # 从队列获取任务（超时5秒）
            task = other_rod_queue.get(timeout=5.0)
            if task is None:  # 收到终止信号
                break
            cargo_id, rod, switch_name = task  # 任务包含：货物ID、推杆名称、传感器名称

            # 执行推杆动作（带延迟）
            push_pull(rod, mqtt_client, delay=PUSH_DELAY)

            # 标记货物处理完成
            tracker.mark_cargo_done(cargo_id)
            other_rod_queue.task_done()  # 标记任务完成

            logger.info(f"分类推杆 {rod} 任务完成，当前队列大小: {other_rod_queue.qsize()}")
        except queue.Empty:
            continue  # 队列为空时继续等待
        except Exception as e:
            logger.error(f"other_rod_worker 错误: {e}")


def start_workers(mqtt_client, tracker):
    """启动工作线程（上货线程+2个分类线程）"""
    workers = []
    # 启动上货推杆线程
    load_thread = threading.Thread(target=load_rod_worker, args=(mqtt_client,), daemon=True)
    load_thread.start()
    workers.append(load_thread)

    # 启动2个分类推杆线程（提高处理效率）
    for _ in range(2):
        t = threading.Thread(target=other_rod_worker, args=(mqtt_client, tracker), daemon=True)
        t.start()
        workers.append(t)
    return workers


def trigger_load(mqtt_client, retry_count=0, max_retries=3):
    """触发上货推杆（控制上货频率，包含重试机制）"""
    global load_timer, last_load_time
    with load_lock:  # 加锁确保线程安全
        load_timer = None
        current_time = time.time()

        # 检查上货间隔是否满足（避免过于频繁）
        if current_time - last_load_time < RECOGNIZE_INTERVAL and last_load_time != 0:
            wait_time = RECOGNIZE_INTERVAL - (current_time - last_load_time)
            logger.info(f"上货间隔未达标，等待 {wait_time:.2f}秒 后触发")
            # 启动定时器，等待间隔达标后再触发
            load_timer = threading.Timer(wait_time, trigger_load, args=(mqtt_client, retry_count, max_retries))
            load_timer.start()
            return

        try:
            # 将上货任务放入队列
            load_rod_queue.put_nowait(load_rod)
            last_load_time = current_time  # 更新上次上货时间
            logger.info("触发上货推杆")

        except queue.Full:
            # 队列满时重试（最多max_retries次）
            if retry_count < max_retries:
                wait_time = min(1.0 * (2 ** retry_count), 5.0)  # 指数退避重试（1s,2s,4s,5s...）
                logger.warning(f"上料队列满，{wait_time:.2f}秒后重试上货（第 {retry_count + 1}/{max_retries} 次）")
                load_timer = threading.Timer(wait_time, trigger_load, args=(mqtt_client, retry_count + 1, max_retries))
                load_timer.start()
            else:
                logger.error("上料队列满，重试达到上限，暂停上货")


class CargoSortingGUI(QMainWindow):
    """桃子分拣系统GUI界面"""
    def __init__(self, mqtt_client, tracker):
        super().__init__()
        self.mqtt_client = mqtt_client  # MQTT客户端实例
        self.tracker = tracker          # 货物追踪器实例
        self.workers = []               # 工作线程列表
        self.system_running = False     # 系统运行状态
        self.init_ui()  # 初始化界面

        # 连接日志信号：将日志消息显示到界面
        qt_handler.log_signal.connect(self.append_log)

        # 设置定时器更新统计信息（每秒更新一次）
        self.stats_timer = QTimer()
        self.stats_timer.timeout.connect(self.update_statistics)
        self.stats_timer.start(1000)

    def init_ui(self):
        """初始化界面布局"""
        self.setWindowTitle("桃子分拣系统控制面板")  # 窗口标题
        self.setGeometry(100, 100, 1000, 700)  # 窗口位置和大小（x,y,宽,高）

        # 创建中央部件（放置所有控件）
        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        # 主布局（水平布局：左侧控制区+右侧信息区）
        main_layout = QHBoxLayout(central_widget)

        # 左侧控制面板（系统控制按钮）
        control_panel = QGroupBox("系统控制")
        control_layout = QVBoxLayout()  # 垂直布局

        # 启动系统按钮
        self.start_btn = QPushButton("启动系统")
        self.start_btn.clicked.connect(self.start_system)  # 绑定点击事件
        control_layout.addWidget(self.start_btn)

        # 停止系统按钮
        self.stop_btn = QPushButton("停止系统")
        self.stop_btn.clicked.connect(self.stop_system)    # 绑定点击事件
        self.stop_btn.setEnabled(False)  # 初始禁用（系统未运行）
        control_layout.addWidget(self.stop_btn)

        # 重置统计按钮
        self.reset_stats_btn = QPushButton("重置统计")
        self.reset_stats_btn.clicked.connect(self.reset_statistics)  # 绑定点击事件
        control_layout.addWidget(self.reset_stats_btn)

        control_panel.setLayout(control_layout)
        main_layout.addWidget(control_panel, 1)  # 占1份宽度

        # 右侧日志和统计面板（垂直布局）
        right_panel = QVBoxLayout()

        # 统计信息区域
        stats_group = QGroupBox("分拣统计")
        stats_layout = QGridLayout()  # 网格布局

        self.ripe_label = QLabel("成熟桃子: 0")      # 成熟桃子统计标签
        self.half_ripe_label = QLabel("半熟桃子: 0")  # 半熟桃子统计标签
        self.raw_label = QLabel("生桃子: 0")          # 生桃子统计标签

        # 放置统计标签（行,列）
        stats_layout.addWidget(self.ripe_label, 0, 0)
        stats_layout.addWidget(self.half_ripe_label, 0, 1)
        stats_layout.addWidget(self.raw_label, 1, 0)

        stats_group.setLayout(stats_layout)
        right_panel.addWidget(stats_group)

        # 日志显示区域
        log_group = QGroupBox("系统日志")
        log_layout = QVBoxLayout()

        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)  # 日志框只读
        self.log_text.setFont(QFont("Consolas", 9))  # 设置字体（等宽字体便于阅读）

        log_layout.addWidget(self.log_text)
        log_group.setLayout(log_layout)
        right_panel.addWidget(log_group)  # 日志区域占剩余空间

        main_layout.addLayout(right_panel, 2)  # 占2份宽度（比控制区宽）

        self.update_button_states()  # 更新按钮状态

    def append_log(self, message):
        """在日志文本框中添加日志消息"""
        self.log_text.moveCursor(QTextCursor.End)  # 光标移到末尾
        self.log_text.insertPlainText(message + '\n')  # 插入消息
        self.log_text.moveCursor(QTextCursor.End)  # 保持光标在末尾（自动滚动）

    def update_statistics(self):
        """更新统计信息显示"""
        stats = self.tracker.get_statistics()  # 获取当前统计
        # 更新标签文本
        self.ripe_label.setText(f"成熟桃子: {stats['ripe']}")
        self.half_ripe_label.setText(f"半熟桃子: {stats['half-ripe']}")
        self.raw_label.setText(f"生桃子: {stats['raw']}")

    def reset_statistics(self):
        """重置统计信息"""
        self.tracker.statistics = {"ripe": 0, "half-ripe": 0, "raw": 0}
        self.tracker.statistics_updated.set()  # 触发更新事件
        logger.info("统计信息已重置")

    def start_system(self):
        """启动系统（初始化设备、启动线程、触发首次上货）"""
        if not self.system_running:
            try:
                # 初始化设备：启动传送带、复位所有推杆
                self.mqtt_client.control_device("conveyor", 'run')
                self.mqtt_client.control_device("rod_control", "all_pull")

                # 启动工作线程（上货+分类）
                self.workers = start_workers(self.mqtt_client, self.tracker)

                # 执行首次上货
                trigger_load(self.mqtt_client)

                self.system_running = True
                logger.info("系统启动成功")

            except Exception as e:
                logger.error(f"系统启动失败: {e}")

        self.update_button_states()  # 更新按钮状态

    def stop_system(self):
        """停止系统（停止设备、终止线程）"""
        if self.system_running:
            try:
                # 停止设备：停止传送带、复位所有推杆
                self.mqtt_client.control_device("conveyor", 'stop')
                self.mqtt_client.control_device("rod_control", "all_pull")

                # 停止工作线程（发送终止信号并等待）
                if self.workers:
                    load_rod_queue.put(None)  # 上货线程终止信号
                    # 分类线程数量 = 总线程数 - 1（上货线程）
                    for _ in range(len(self.workers) - 1):
                        other_rod_queue.put(None)  # 分类线程终止信号
                    # 等待线程结束（超时2秒）
                    for w in self.workers:
                        w.join(timeout=2.0)

                self.system_running = False
                logger.info("系统已停止")

            except Exception as e:
                logger.error(f"系统停止失败: {e}")

        self.update_button_states()  # 更新按钮状态

    def update_button_states(self):
        """根据系统状态更新按钮可用性"""
        self.start_btn.setEnabled(not self.system_running)  # 系统未运行时可启动
        self.stop_btn.setEnabled(self.system_running)       # 系统运行时可停止

    def closeEvent(self, event):
        """窗口关闭事件（确保系统正常停止）"""
        if self.system_running:
            self.stop_system()  # 关闭窗口前停止系统
        event.accept()  # 接受关闭事件


# 主程序入口
def main():
    # 创建Qt应用实例
    app = QApplication(sys.argv)

    # 初始化MQTT客户端（连接参数：IP、端口、用户名、密码、超时时间）
    mqtt_client = Mqtt_Clt("127.0.0.1", 21883, "bb", "aa", 60)

    # 初始化货物追踪器
    tracker = CargoTracker()

    # 创建并显示界面
    gui = CargoSortingGUI(mqtt_client, tracker)
    gui.show()

    # 启动定期清理线程（每30秒清理超时货物）
    def periodic_cleanup():
        while True:
            tracker.cleanup_old_cargos()
            time.sleep(30)

    cleanup_thread = threading.Thread(target=periodic_cleanup, daemon=True)
    cleanup_thread.start()

    # 消息处理循环（单独线程，处理MQTT接收的消息）
    def message_loop():
        try:
            while True:
                try:
                    # 从MQTT队列获取消息（超时1秒，避免阻塞）
                    msg = mqtt_client.mqtt_queue.get(timeout=1.0)
                except queue.Empty:
                    continue  # 队列为空时继续等待

                # 处理图像消息（用于桃子分类）
                if 'image' in msg:
                    try:
                        logger.info("收到图像，开始解码")
                        # 解码Base64图像数据
                        image_data = base64.b64decode(msg['image'])
                        image_array = np.frombuffer(image_data, np.uint8)
                        image = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
                        if image is None:
                            logger.error("图像解码失败")
                            continue

                        # 使用YOLO模型预测桃子成熟度
                        try:
                            classification = predict_yolo(image)
                        except Exception as e:
                            logger.error(f"YOLO 推理失败: {e}")
                            continue

                        logger.info(f"识别结果: {classification}")
                        # 若分类有效，创建货物并触发上货
                        if classification in CLASS_SWITCH_MAP:
                            cargo_id = tracker.create_new_cargo(classification)
                            if gui.system_running:  # 系统运行时才触发上货
                                trigger_load(mqtt_client)
                        else:
                            logger.warning(f"忽略无效分类: {classification}")
                    except Exception as e:
                        logger.error(f"图像处理错误: {e}")
                        continue

                else:
                    # 处理传感器消息（判断哪个传感器被触发）
                    logger.info(f"收到传感器消息: {msg}")
                    # 筛选出被触发的有效传感器（值为True）
                    valid_switches = [s for s in CLASS_SWITCH_MAP.values() if msg.get(s, False)]
                    if not valid_switches:
                        logger.warning(f"无有效传感器触发，忽略消息: {msg}")
                        continue

                    # 按预设顺序排序（处理多个传感器同时触发的情况）
                    sorted_valid_switches = sorted(valid_switches, key=lambda s: SENSOR_ORDER.index(s))
                    if len(valid_switches) > 1:
                        logger.warning(f"异常: 多个传感器同时触发 {valid_switches}，按顺序处理: {sorted_valid_switches}")
                    else:
                        logger.info(f"有效传感器: {sorted_valid_switches[0]}（硬件True）")

                    # 为每个触发的传感器匹配货物并添加分类推杆任务
                    for active_switch in sorted_valid_switches:
                        cargo_id = tracker.update_cargo_switch(active_switch)
                        if cargo_id:
                            # 获取对应分类的推杆
                            rod_to_push = CLASS_ROD_MAP.get(tracker.cargo_map[cargo_id]["classification"])
                            if rod_to_push:
                                logger.info(f"货物ID: {cargo_id} 触发分类推杆: {rod_to_push}")
                                task = (cargo_id, rod_to_push, active_switch)  # 任务信息
                                try:
                                    other_rod_queue.put_nowait(task)  # 放入分类队列
                                except queue.Full:
                                    logger.warning("分类推杆队列满，1秒后重试放入任务")
                                    # 1秒后重试放入任务
                                    threading.Timer(1.0, lambda: other_rod_queue.put_nowait(task)).start()
                            else:
                                logger.error(f"货物ID: {cargo_id} 无对应推杆")

        except Exception as e:
            logger.error(f"消息处理循环错误: {e}")

    # 启动消息处理线程
    message_thread = threading.Thread(target=message_loop, daemon=True)
    message_thread.start()

    # 运行Qt主循环（界面事件处理）
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()